package com.heima.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.common.exception.CustomException;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dtos.AssociateSearchDto;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.model.search.vos.SearchArticleVo;
import com.heima.search.pojos.ApAssociateWords;
import com.heima.search.pojos.ApUserSearch;
import com.heima.search.service.SearchService;
import com.heima.utils.thread.AppThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author brianxia
 * @version 1.0
 * @date 2022-11-19 10:27
 */
@Service
@Slf4j
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public List<SearchArticleVo> search(UserSearchDto dto) {
        try {

            SearchRequest request = new SearchRequest("app_info_article");
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

            //查询条件 单一条件创建QueryBuilders设置  多个条件 布尔查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //1.must 2.should  匹配度计算算分 must = and  should = or
            //3.filter 4.must_not 不计算算分 filter 直接按条件过滤 > 50   must_not 取反 非!  >50 = <= 50

            //分词查询 标题和内容同时进行分词查询   时间条件查询   <  最小时间
            boolQueryBuilder.must(QueryBuilders.
                    queryStringQuery(dto.getSearchWords()).field("title").field("content")
                    .defaultOperator(Operator.OR)
            );

            //时间范围查询 小于当前页最后一条数据时间，转换成毫秒值
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("publishTime")
                    .lt(dto.getMinBehotTime().getTime()));

            searchSourceBuilder.query(boolQueryBuilder);
            //分页 按时间筛选，永远第一条
            searchSourceBuilder.from(0).size(dto.getPageSize());    //limit 0,10 =  limit 10
            //高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title").preTags("<font style='color: red; font-size: inherit;'>")
                    .postTags("</font>");
            searchSourceBuilder.highlighter(highlightBuilder);
            //排序 时间倒序
            searchSourceBuilder.sort("publishTime", SortOrder.DESC);

            request.source(searchSourceBuilder);
            SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            //封装响应结果
            SearchHit[] hits = search.getHits().getHits();
            List<SearchArticleVo> searchArticleVos = new ArrayList<>();
            if (hits != null) {
                for (SearchHit hit : hits) {
                    String source = hit.getSourceAsString();
                    SearchArticleVo searchArticleVo = JSON.parseObject(source, SearchArticleVo.class);
                    //给htitle设置上原始标题避免高亮为空
                    searchArticleVo.setH_title(searchArticleVo.getTitle());
                    //处理高亮
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                    if (highlightFields != null && highlightFields.containsKey("title")) {
                        HighlightField title = highlightFields.get("title");
                        if (title != null) {
                            Text[] fragments = title.getFragments();
                            if (fragments != null && fragments.length > 0) {
                                Text fragment = fragments[0];
                                String string = fragment.string();
                                searchArticleVo.setH_title(string);
                            }
                        }
                    }
                    searchArticleVos.add(searchArticleVo);
                }
            }

            return searchArticleVos;
        } catch (Exception e) {

            log.error("查询es出错", e);
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR);
        }
    }

    @Override
    public void syncArticleToEs(SearchArticleVo searchArticleVo) {
        try {
            restHighLevelClient.index(new IndexRequest("app_info_article").id(searchArticleVo.getId().toString())
                    .source(JSON.toJSONString(searchArticleVo), XContentType.JSON), RequestOptions.DEFAULT
            );
        } catch (Exception e) {
            log.error("写入es失败");
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR);
        }

    }

    @Override
    @Async //异步处理
    public void saveSearchHistory(String searchWords,Integer userId) {

        //1.查询搜索记录
        List<ApUserSearch> datas = mongoTemplate.
                find(Query.query(Criteria.where("userId").is(userId))
                        .with(Sort.by(Sort.Direction.DESC,"createdTime")), ApUserSearch.class);

        //2.判断是否存在 stream流
        List<ApUserSearch> list = datas.stream().filter(x -> x.getKeyword().equals(searchWords))
                .collect(Collectors.toList());

        if(list.size() > 0){
            //如果存在，更新时间到最新
            ApUserSearch apUserSearch = list.get(0);
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }

        //3.判断数据量是否超过10条
        if(datas.size() >= 10){
            //超过10条，替换最后一条数据
            ApUserSearch apUserSearch = datas.get(datas.size() - 1);
            apUserSearch.setKeyword(searchWords);
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
        }else{
            //直接保存
            ApUserSearch apUserSearch = new ApUserSearch();
            apUserSearch.setUserId(userId);
            apUserSearch.setCreatedTime(new Date());
            apUserSearch.setKeyword(searchWords);
            mongoTemplate.save(apUserSearch);
        }

    }

    @Override
    public List<ApUserSearch> loadUserSearch() {
        //获取用户id
        Integer id = AppThreadLocalUtil.getUser().getId();
        List<ApUserSearch> datas = mongoTemplate.
                find(Query.query(Criteria.where("userId").is(id))
                        .with(Sort.by(Sort.Direction.DESC,"createdTime")), ApUserSearch.class);

        return datas;
    }

    @Override
    public void delUserSearch(String id) {
        mongoTemplate.remove(Query.query(Criteria.where("_id").is(id)),ApUserSearch.class);
    }

    @Override
    public List<ApAssociateWords> loadAssociate(AssociateSearchDto dto) {

        return mongoTemplate.find(Query.query(Criteria.where("associateWords")
                .regex(".*" + dto.getSearchWords() + ".*")).limit(dto.getPageSize()), ApAssociateWords.class);
    }
}
